// $License: ZHIGANG.LI@LIVE.CN
//--------------------------------------------------------------------------------
/**
  @file fep_lib
  @copyright ZHIGANG.LI@LIVE.CN
  @author ZHIGANG.LI
*/
//--------------------------------------------------------------------------------
// Variables and Constants

global mapping mFEP = makeMapping(); // mFEP 存储本地的FEP和远程的FEP工作状态信息。

// 周期从pmon获得各个进程的工作状态存储到mFEP。0：停止，1：正在运行，2：正常运行，3：堵塞异常
// auth:zhigang.li
void FEP_getManagersState(string PMON_HOST, uint port)
{
  string key ;

  if (PMON_HOST == "localhost")
  {
    key = "localhost";
  }
  else
  {
    key = "remote";
  }

  if (!mappingHasKey(mFEP, key))
  {
    mFEP[key] = makeMapping();
  }

  while (1)
  {
    // 先执行ping，如果没有网络，则无需tcpOpen通讯。
    if (_fep_ping(FEP_PARTNER_HOSTNAME) == 0)
    {
      DebugN("PMON_HOST:" + FEP_PARTNER_HOSTNAME + " 无法ping通,20s后自动重试！");
      delay(20);
      continue;
    }

    int socket;
    // 打开端口
    socket =  tcpOpen(PMON_HOST, port);
    bool conn = (socket == -1) ? false : true;

    while (conn)
    {
      string tmp = "", data = ""; // 初始化，且清空数据存储

      if (tcpWrite(socket, "##MGRLIST:STATI\n")  == -1)
      {
        conn = false;
		
		DebugN("PMON_HOST:无法向对方pmon请求数据！20s后自动重试!");
		delay(20);

        break;
      }

      // 等待100ms，网络从伙伴获得回传的数据。
      delay(0, 100);

      // 解析回传的TCP结果
      for (int i = 1; i <= 2; i++)
      {
        tmp = "";

        // 读取tcp
        if (tcpRead(socket, tmp, 1) == -1)
        {
          conn = false;
		  DebugN("PMON_HOST:无法读取对方的pmon的数据！20s后自动重试!");		
		  delay(20);

          break;
        }

        // 最多2个tcp 包!
        data += tmp;
      }

      if (conn == false) 
	  { 
		  DebugN("PMON_HOST:连接失败！20s后自动重试!");		
		  delay(20);
		  break;// 再次检测网络状态
	  }

      // 解析data数据包 见附件四、
      dyn_string rows = strsplit(data, "\n");

      int iRowIndex = 0;

      // 如data数据包示例，从2nd行开始到总行数-2,是有效数据
      for (int i = 2; i <= dynlen(rows) - 2; i++)
      {

        if (!mappingHasKey(mFEP[key], "R-" + iRowIndex)) //e.g. mFEP["localhost"]["ROW-0"] = "2"
        {
          mFEP[key][ "R-" + iRowIndex] = makeMapping();
        }

        dyn_string columns = strsplit(rows[i], ";"); // 根据';'符号来分割字符串。

        mFEP[key][ "R-" + iRowIndex]["PMON_STATE"] = (int)columns[1];

        iRowIndex++;
      }

      // 处理输出的日志信息和DEBUG信息
      DebugFN("fepdbg", "【收到 PMON_HOST:】 " + PMON_HOST, data);
      DebugFN("fepdbg", "【解析 mFEP:】",  mFEP);
    }

    tcpClose(socket);
    mFEP[key] = makeMapping(); //RESET THIS HOST INFORMATION!
    delay(10);
    DebugN("与主机: " + PMON_HOST + " 的PMON无法建立 TCP/IP Sccket 连接，10s后自动重试...");
  }
}


// 功能：周期性的执行ping指令，对PLC_HOSTNAME进行ping的结果解析，存储到mFEP
// TARGET_HOSTNAME: PLC或类似远程需要进行ping的主机或IP地址，如 192.168.0.13 或 hosts定义的PLC_REMOTE_001
// iRowIndex, 从PMON的第一行（0开始）的序列，如，数字2，代表第三行。
// DEFAULT_IN_FEP1： 令该manager默认运行所在的pmon的主机，如 DEFAULT_IN_FEP1 = true， 运行在 FEP1 ，否则运行在 FEP2 的主机。
void FEP_getTargetState(string TARGET_HOSTNAME, int iRowIndex, string DEFAULT_IN_FEP)
{
  while (1)
  {
    // 将对应的index+1的位置的数字置为 true：ok  false:error



    if (mappingHasKey(mFEP, "localhost"))
    {
      if (mappingHasKey(mFEP["localhost"],  "R-" + iRowIndex))
      {
        if (mappingHasKey(mFEP["localhost"][ "R-" + iRowIndex], "PMON_STATE"))
        {

          if (!mappingHasKey(mFEP["localhost"][ "R-" + iRowIndex ], "OS_PING_RETURN"))
          {
            mFEP["localhost"][ "R-" + iRowIndex ]["OS_PING_RETURN"] = makeDynInt();
          }

          dyn_int diPingRet;
          diPingRet = mFEP["localhost"][ "R-" + iRowIndex ]["OS_PING_RETURN"];
          dynAppend(diPingRet,  _fep_ping(TARGET_HOSTNAME));

          if (dynlen(diPingRet) > 10)
          {
            dynRemove(diPingRet, 1);
          }

          mFEP["localhost"][ "R-" + iRowIndex ]["OS_PING_RETURN"] = diPingRet;
          mFEP["localhost"][ "R-" + iRowIndex ]["DEFAULT_IN_FEP"] =  DEFAULT_IN_FEP;
          mFEP["localhost"][ "R-" + iRowIndex ]["TARGET_HOSTNAME"] =  TARGET_HOSTNAME;
          mFEP["localhost"][ "R-" + iRowIndex ]["INDEX"] =  iRowIndex;
        }
      }
    }

    // 周期执行！
    delay(2);
  }
}

// 看门狗程序，周期性的进行逻辑判断来启动或停止manager
//    plc   local  remote action
//    1       0      0    start fep;
//    0       2      0    kill fep1;
FEP_Watchdog()
{
  string comment; // 注释

  while (1)
  {

    // 首次先delay ， 周期 2s
    delay(5);

    //遍历本地的localhost的状态
    for (int i = 1; i <= mappinglen(mFEP["localhost"]); i++)
    {
      // row_key： R-0
      string row_key = mappingGetKey(mFEP["localhost"], i); // R-0,第一行    R-1, 第二行...

      /*mRow, e.g.
      {
        "PMON_STATE": 2,
        "OS_PING_RETURN": true,
        "TARGET_HOSTNAME": "www.winccoa.top",
        "DEFAULT_IN_FEP": "主机",
        "INDEX": 4,
        "DELAYTIME(ms)": 13.0
      }

      or
      {
        "PMON_STATE": 2
      }
      */
      // 遍历mFEP["localhost"], 含有key="OS_PING_RETURN"的字段，对于已经配置了负载均衡的驱动的item进行负载均衡的逻辑控制。
      if (mappingHasKey(mFEP["localhost"][row_key], "OS_PING_RETURN"))
      {
        // 遍历开始!


        // 开始优先级0： 对方的 fep.ctl没有运行 + 本地可以ping通 =强制启动本地INDEX ----- level 0
        // 前提的设定：第0行必须是pmon， 第1行必须是FEP所在的行！
        if (mappingHasKey(mFEP["remote"], "R-1"))      // 远程R-1行，fep.ctl所在的行
        {
          // 含义：获得了远程的 pmon数据，但远程的fep.ctl没有运行
          if (mappingHasKey(mFEP["remote"]["R-1"], "PMON_STATE")
              && mFEP["remote"]["R-1"]["PMON_STATE"] == 0)
          {
            if (dynSum(mFEP["localhost"][row_key]["OS_PING_RETURN"]) > 5   // 含义：本地 ping target 10次至少5次成功
                && mFEP["localhost"][row_key]["PMON_STATE"] == 0) // 含义：本地没有运行
            {

              comment =  "获得了远程FEP "+FEP_PARTNER_HOSTNAME+"的 pmon 数据,\n" +
                         "但远程FEP " +FEP_PARTNER_HOSTNAME+ "的 fep.ctl 似乎没有运行,\n" +
                         "本地可以 ping 成功目标: " + mFEP["localhost"][row_key]["TARGET_HOSTNAME"] + ",\n" +
                         "尝试启动..."  ;

              _fep_cmd_start(mFEP["localhost"][row_key]["INDEX"],   0, comment);

            }

          }
          else if (mappingHasKey(mFEP["remote"]["R-1"], "PMON_STATE")
                   && mFEP["remote"]["R-1"]["PMON_STATE"] == 2)
          {

            // 开始优先级1：远程 fep.ctl启动  ----- level 1
            if (mappingHasKey(mFEP["remote"], row_key))
            {
              // 对方0 + 本地0 + 本地ping OK = 启动本地
              if (mFEP["remote"][row_key]["PMON_STATE"] == 0  // 远程Driver 未启动！
                  && mFEP["localhost"][row_key]["PMON_STATE"] == 0
                  && dynSum(mFEP["localhost"][row_key]["OS_PING_RETURN"]) == 10)
              {
                comment =  "获得了远程FEP "+FEP_PARTNER_HOSTNAME+"的的 pmon 数据,\n" +
                           "远程FEP的检测到驱动未启动，行：" + row_key + "\n" +
                           "本地可以 ping 成功目标: " + mFEP["localhost"][row_key]["TARGET_HOSTNAME"]  + " \n" +
                           "本地尝试启动..."  ;

                _fep_cmd_start(mFEP["localhost"][row_key]["INDEX"], 1, comment);

              }
              else
              {
                // 开始优先级2：
                // 对方0 + 本地2 + 本地ping Error = 停止本地
                if (mFEP["remote"][row_key]["PMON_STATE"] == 0 //// 远程Driver 未启动！
                    && dynSum(mFEP["localhost"][row_key]["OS_PING_RETURN"]) <= 5
                    && dynlen(mFEP["localhost"][row_key]["OS_PING_RETURN"]) == 10
                    && mFEP["localhost"][row_key]["PMON_STATE"] == 2)
                {
                  // START LOCAL

                  comment =  "获得了远程FEP "+FEP_PARTNER_HOSTNAME+"的的 pmon 数据,\n" +
                             "远程FEP的检测到驱动未启动，行：" + row_key + "\n" +
                             "本地无法 ping 通目标: " + mFEP["localhost"][row_key]["TARGET_HOSTNAME"]  + " \n" +
                             "停止本地Manager..."  ;

                  _fep_cmd_stop(mFEP["localhost"][row_key]["INDEX"], 2, comment);
                }
                // （graceful）优先级11： -- lelve 11
                // 对方2 + 本地0 + 本地已经ping 10次合格 + 默认应当运行在本地 = 停止远程，开启本地。
                else if (mFEP["remote"][row_key]["PMON_STATE"] == 2  // 远程Driver 已经启动！
                         && mFEP["localhost"][row_key]["PMON_STATE"] == 0
                         && dynSum(mFEP["localhost"][row_key]["OS_PING_RETURN"]) == 10
                         && mFEP["localhost"][row_key]["DEFAULT_IN_FEP"] == THIS_HOST
                        )
                {

                  comment =  "获得了远程FEP "+FEP_PARTNER_HOSTNAME+"的的 pmon 数据,\n" +
                             "远程FEP的检测到驱动已经启动，行：" + row_key + "\n" +
                             "本地也可以 ping 通目标: " + mFEP["localhost"][row_key]["TARGET_HOSTNAME"]  + " \n" +
                             "尝试停止远程Manager..."  ;

                  _remote_fep_cmd_stop(mFEP["localhost"][row_key]["INDEX"], 11, comment);

                  comment =  "获得了远程FEP "+FEP_PARTNER_HOSTNAME+"的的 pmon 数据,\n" +
                             "远程FEP的检测到驱动已经启动，行：" + row_key + "\n" +
                             "本地也可以 ping 通目标: " + mFEP["localhost"][row_key]["TARGET_HOSTNAME"]  + " \n" +
                             "尝试启动本地Manager..."  ;

                  _fep_cmd_start(mFEP["localhost"][row_key]["INDEX"], 11, comment);
                }

                // （graceful）优先级12： -- lelve 12
                // 对方2 + 本地2 + 本地已经ping 2次合格 + 默认应当运行在本地 = 停止远程，开启本地。
                else if (mFEP["remote"][row_key]["PMON_STATE"] == 2  // 远程Driver 已经启动！
                         && mFEP["localhost"][row_key]["PMON_STATE"] == 2
                         && dynSum(mFEP["localhost"][row_key]["OS_PING_RETURN"]) == 2
                         && mFEP["localhost"][row_key]["DEFAULT_IN_FEP"] == THIS_HOST
                        )
                {

                  comment =  "获得了远程FEP "+FEP_PARTNER_HOSTNAME+"的的 pmon 数据,\n" +
                             "远程FEP的检测到驱动已经启动，行：" + row_key + "\n" +
                             "本地也可以 ping 通目标: " + mFEP["localhost"][row_key]["TARGET_HOSTNAME"]  + " \n" +
                             "尝试停止远程Manager..."  ;

                  _remote_fep_cmd_stop(mFEP["localhost"][row_key]["INDEX"], 11, comment);

                }


                // （graceful）优先级12： -- lelve 12
                // 对方0 + 本地2 + 本地已经 ping 10 次合格 + 默认应当运行在远程 = 停止本地。
                else if (mFEP["remote"][row_key]["PMON_STATE"] == 0  // 远程Driver 已经启动！
                         && mFEP["localhost"][row_key]["PMON_STATE"] == 2
                         && dynSum(mFEP["localhost"][row_key]["OS_PING_RETURN"]) == 10
                         && mFEP["localhost"][row_key]["DEFAULT_IN_FEP"] != THIS_HOST
                        )
                {

                  comment =  "获得了远程FEP "+FEP_PARTNER_HOSTNAME+"的的 pmon 数据,\n" +
                             "远程FEP的检测到驱动没有启动，行：" + row_key + "\n" +
                             "本地可以 ping 通目标: " + mFEP["localhost"][row_key]["TARGET_HOSTNAME"]  + "，默认应当运行在远程FEP "+FEP_PARTNER_HOSTNAME+"\n" +
                             "尝试停止本地Manager..."  ;

                  _fep_cmd_stop(mFEP["localhost"][row_key]["INDEX"], 12, comment);

                }

              }

            }
            else
            {

              comment =  "获得了远程FEP "+FEP_PARTNER_HOSTNAME+"的的 pmon 数据,\n" +
                         "但远程FEP的对应的行号似乎没有配置！行：" + row_key + "\n" +
                         "本地可以 ping 成功目标: " + mFEP["localhost"][row_key]["TARGET_HOSTNAME"] + ",\n" +
                         "尝试启动..."  ;

              _fep_cmd_start(mFEP["localhost"][row_key]["INDEX"], 1, comment);
            }
          }
          else
          {
            comment =  "获得了远程FEP "+FEP_PARTNER_HOSTNAME+"的的 pmon 数据,\n" +
                       "但远程FEP似乎只有pmon！\n" +
                       "本地可以 ping 成功目标: " + mFEP["localhost"][row_key]["TARGET_HOSTNAME"] + ",\n" +
                       "尝试启动..."  ;

            _fep_cmd_start(mFEP["localhost"][row_key]["INDEX"], 0, comment);
          }
        }
        else
        {
          // 含义：无法获得远程的PMON数据
          // 功能：两个FEP进行黑箱竞争性启动
          // 本地0 + 本地ping通  = 本地启动
          if (mFEP["localhost"][row_key]["PMON_STATE"] == 0
              && dynSum(mFEP["localhost"][row_key]["OS_PING_RETURN"]) > 1
             )
          {
            comment =  "无法获得远程FEP "+FEP_PARTNER_HOSTNAME+"的的 pmon 数据, \n" +
                       "可能是ping不通，网络故障,\n" +
                       "将以黑箱竞争式来启动驱动: " + mFEP["localhost"][row_key]["TARGET_HOSTNAME"] + ",\n" +
                       "尝试启动..."  ;

            _fep_cmd_start(mFEP["localhost"][row_key]["INDEX"], 0, comment);
          }
        }

      }


    }


    // 存储到日志分析。
    file f = fopen(LOG_REL_PATH + "fep.json", "w+");
    fputs(jsonEncode(mFEP, false), f);
    fflush(f);
    fclose(f);

  }
}

// fep ping target return the state:0=errro, 1=ping ok.
int _fep_ping(string target)
{

  string command_return_string; // 存储ping返回的字符串
  string os_command; // 存储操作系统的控制台指令ping

  string lossSpecificStr; // windows, linux 对于丢失包的特征字符串。windows:"(0% loss)"  Linux:", 0% packet loss"

  if (_WIN32)
  {
    os_command = "chcp 437 && ping -w 500 -n 1 " + target ;
    // windows
    lossSpecificStr = "(0%";

    /* e.g. 见附件二、
    */

  }
  else
  {
    os_command = "ping -w 1 -c 1 " + target;
    lossSpecificStr = ", 0%";
    // e.g. 见附件三、
  }

  // 向系统发送ping指令
  system(os_command, command_return_string);

  // 处理输出的日志信息和DEBUG信息
  DebugFN("fepdbg", "【发送 os_command :】",  os_command);
  DebugFN("fepdbg", "【收到 command_return_string :】",  command_return_string);

  // 判断返回字串中是否含(0% loss)，代表通讯OK
  return strpos(command_return_string, lossSpecificStr) > 0 ? 1 : 0 ;

}

// send a start manager command to the pmon
_fep_cmd_start(int INDEX, int priority, string comment = "")
{
  DebugTN("第" + INDEX + "行的manager将启动运行！优先级:" + priority + "\n注释：" + comment);
  // START LOCAL ALL
  int s = tcpOpen("localhost", FEP_LOCAL_PMONPORT);
  tcpWrite(s, "##SINGLE_MGR:START " +  INDEX + "\n");

  delay(0, 100);

  tcpClose(s);
}

// send a stop manager command to the pmon
_fep_cmd_stop(int INDEX, int priority, string comment = "")
{


  DebugTN("第" + INDEX + "行的manager将关闭！优先级：" + priority + "\n注释：" + comment);
  int s = tcpOpen("localhost", FEP_LOCAL_PMONPORT);
  tcpWrite(s, "##SINGLE_MGR:STOP " +  INDEX + "\n");
  delay(0, 100);
  tcpClose(s);
}

// send a stop remote fep manager command
_remote_fep_cmd_stop(int INDEX, int priority, string comment = "")
{
  DebugTN("远程" + FEP_PARTNER_HOSTNAME + ",第" + INDEX + "行的manager将关闭！优先级：" + priority + "\n注释：" + comment);
  int s = tcpOpen(FEP_PARTNER_HOSTNAME, FEP_PARTNER_PMONPORT);
  tcpWrite(s, "##SINGLE_MGR:STOP " +  INDEX + "\n");
  delay(0, 100);
  tcpClose(s);
}


fepctl()
{

  // 第一行：读取 localhost中的 PMON 的manager运行状态，必须使用localhost，不可以用127.0.0.1
  startThread("FEP_getManagersState", "localhost", FEP_LOCAL_PMONPORT);

  // 第二行：读取远程FEP 的 PMON 中 manager 运行状态
  startThread("FEP_getManagersState", FEP_PARTNER_HOSTNAME, FEP_PARTNER_PMONPORT);

  // 逐个将需要被管理的FEP罗列在这里。
  // 首行是0行，从第0开始。 "主机" 代表默认情况下，即主备都是可以通讯的正常情况运行在主机， 相反，“备机” 是默认运行在备机。

  for (int i = 1; i <= dynlen(dmPmonConfig); i++)
  {
    startThread("FEP_getTargetState",
                dmPmonConfig[i]["target"],
                dmPmonConfig[i]["index"],
                dmPmonConfig[i]["default"]);

    DebugN("正在启动...", "目标：" + dmPmonConfig[i]["target"],
           "第" + dmPmonConfig[i]["index"] + "行",
           "默认:" + dmPmonConfig[i]["default"]);

    delay(0, 10);
  }


  // 守护进程（看门狗）
  startThread("FEP_Watchdog");

}
